package org.clockwise.feature;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.clockwise.util.GlobalTFIDF;
import org.clockwise.util.MyStrUtil;

import Listwise.PredictLinearNeuralModel;

/**
 * This class holds a single query, including the keyword,
 * the link (if there is one), and the context information.
 * 
 * The core process of producing feature vector goes on here.
 * 
 * @author WangYan
 *
 */
public class Query {
	
	public static int QUERY_ID = 1;
	public static HashMap<String, Double> tfidf = null;
	
	public int queryId = QUERY_ID;
	
	public TextLabel keyword;				// the keyword to find a link for
	public QueryContext context;			// the context of the keyword
	public List<Account> potential;			// the set of potential accounts
	public List<double[]> featureList;		// the list of feature vectors, the first one should be the right link
	
	public boolean isLinked;
	public Account correctUser;
	public boolean findCorrect = true;
	
	public Query(TextLabel keyword, QueryContext context, AccountPool pool) {
		
		if (tfidf == null) {
			tfidf = GlobalTFIDF.getUserTFIDF();
		}
		
		this.keyword = keyword;
		this.context = context;
		isLinked = keyword.isLinked;
		
		if (isLinked) {
			if (!pool.userDict.containsKey(keyword.linkId)) {
				System.out.println("Fatal Error! Cannot find correct id from user dict! Id: " + keyword.linkId.toString());
			}
			correctUser = pool.userDict.get(keyword.linkId);
		}
		else
			correctUser = null;
		
		featureList = new ArrayList<double[]>();
		
		potential = pool.getPotentialList(keyword.key, keyword.segWords, keyword.segLabels);
		getCorrectFirst(potential);
		Iterator<Account> iter = potential.iterator();
		while (iter.hasNext()) {
			Account user = iter.next();
			featureList.add(this.produceFeature(user));
		}
		
		
		if (potential.size() == 0 || !potential.get(0).id.equals(keyword.linkId)) {
			if (isLinked) {
//				System.out.print("Cannot find right id for " + keyword.key + "\t(");
//				for (int i = 0; i < keyword.segWords.length; i++) {
//					System.out.print(keyword.segWords[i] + "/");
//				}
//				System.out.println(")");
//				System.out.println("Candidate list size: " + potential.size());
//				for (int i = 0; i < potential.size(); i++) {
//					System.out.println(potential.get(i).toString());
//				}
				if (correctUser == null) {
					System.out.println("Correct user is null......");
				}
//				else {
//					System.out.println("Correct user is: " + correctUser.toString());
//				}
				this.findCorrect = false;
			}
		}
		if (featureList.size() > 0)
			PredictLinearNeuralModel.NormalizeData(featureList);
	}
	
	/*
	 * Find the correct linked user in the potential list and move it to
	 * index 0 in the list.
	 */
	public void getCorrectFirst(List<Account> potential) {
		if (potential == null || correctUser == null) {
			return;
		}
		if (potential.remove(correctUser)) {
			potential.add(0, correctUser);
		}
		else {
//			System.out.println("No correct user");
		}
	}
	
	/*
	 * The core process of producing feature vector.
	 */
	public double[] produceFeature(Account user) {
		List<Double> vector = new ArrayList<Double>();
		
		String key = this.keyword.key;
		
		// all boolean features are assigned 1 if false, 2 if true.
		
		// dimension 1 : 	whether key word is a substring of user name, {1, 0}
		
		if (user.name.contains(key)) {
			vector.add(new Double(1));
		}
		else {
			vector.add(new Double(0));
		}
		
		// dimension 2 : 	whether user name is a substring of key word, {1, 0}
		
		if (key.contains(user.name)) {
			vector.add(new Double(1));
		}
		else {
			vector.add(new Double(0));
		}
		
		// dimension 3 : 	whether key word is a substring of user info, {1, 0}
		//					if the user has no info, assign 1 
		
		if (user.info.contains(key)) {
			vector.add(new Double(1));
		}
		else {
			vector.add(new Double(0));
		}
		
		// LCS is short for Longest Common Substring
		// dimension 4 :	length of LCS of key word and user name, [0, +infinity)
		// dimension 5 :	miss length of LCS and user name, [0, +infinity)
		int LCSlength = MyStrUtil.comSubstring(key, user.name).length();
		vector.add(new Double(LCSlength));
//		vector.add(new Double(user.name.length() - LCSlength));
		
		// dimension 6 : 	length of LCS of key word and user info, [0, +infinity)
		vector.add(new Double(MyStrUtil.comSubstring(key, user.info).length()));
		
		// dimension 7 :	number of words in context contained in user info or name, [0, +infinity)
		// dimension 8 :	the sum of reversed distance of words, [0, +infinity)
		//					eg. AB<C>DE, if A and D are in user info of name, then 
		//						sum = 1 + 1/dis(A,C) + 1/dis(C,D) = 1 + 1/2 + 1/1 = 2.5
		// dimension 9 :	the number of missed words in context, [0, +infinity)
		// dimension 10:	the number of nouns that user info and context share, [0, +infinity)
		// dimension 11:	the sum of reversed distance of nous, [0, +infinity)
		String[] wordSeg = this.context.words;
		String[] labelSeg = this.context.labels;
		double wordCount = 0, nounCount = 0;
		double wordDis = 0.0, nounDis = 0.0; 
		int rank = context.findWordRank(keyword.beginPos);
		for (int i = 0; i < wordSeg.length; i++) {
			if (user.info.contains(wordSeg[i]) || user.name.contains(wordSeg[i])) {
				if (tfidf.containsKey(wordSeg[i])) {
					wordCount += tfidf.get(wordSeg[i]);
					if (i != rank)
						wordDis += tfidf.get(wordSeg[i]) / Math.abs(i - rank);
				}
//				wordCount++;
//				if (i != rank)
//					wordDis += 1.0 / Math.abs(i - rank);
				if (labelSeg[i].startsWith("n")) {
					if (tfidf.containsKey(wordSeg[i])) {
						nounCount += tfidf.get(wordSeg[i]);
						if (i != rank)
							nounDis += tfidf.get(wordSeg[i]) / Math.abs(i - rank);
					}
//					nounCount++;
//					if (i != rank)
//						nounDis += 1.0 / Math.abs(i - rank);
				}
			}
		}
		
		vector.add(new Double(wordCount));
		vector.add(new Double(wordDis));
//		vector.add(new Double(context.labels.length - wordCount));
//		vector.add(new Double(nounCount));
//		vector.add(new Double(nounDis));
		
		
		// dimension 12:	number of publish of user, [0, +infinity)
//		vector.add(new Double(Math.log(1+user.publish)));
//		vector.add(new Double(user.publish));
		
		// dimension 13:	number of out links of user, [0, +infinity)
//		vector.add(new Double(Math.log(1+user.outlink)));
		vector.add(new Double(user.outlink));
		
		// dimension 14 :	number of in links of user, [0, +infinity)
//		vector.add(new Double(Math.log(1+user.inlink)));
		vector.add(new Double(user.inlink));
		
		// dimension 15:	number of other key words that is substring of user name or info, [0, infinity)
		// dimension 16: 	sum of length of LCSs of other key words and the user name or info, [0, infinity)
		int count = 0;
		int length = 0;
		List<TextLabel> others = this.context.labelList;
		for (Iterator<TextLabel> iter = others.iterator(); iter.hasNext(); ) {
			TextLabel tl = iter.next();
			if (this.keyword.equals(tl)) {
				continue;
			}
			else {
				length += MyStrUtil.comSubstring(tl.key, user.name).length();
				length += MyStrUtil.comSubstring(tl.key, user.info).length();
				if (user.info.contains(tl.key) || user.name.contains(tl.key)) {
					count++;
				}
			}
		}
		
//		vector.add(new Double(count));
//		vector.add(new Double(length));
		
		
		// dimension 17:	whether the user contains some words like "官方","会","吧", {1, 0}
		
		if (user.info.contains("官方") || user.info.contains("会") || user.info.contains("吧")) {
//			vector.add(new Double(1));
		}
		else {
//			vector.add(new Double(0));
		}
		
		
		// dimension 18:	whether the key word is a person or others, person = 1, others = 0
		if (keyword.labelType.equals(TextLabel.PERSON_LABEL)) {
//			vector.add(new Double(1));
		}
		else {
//			vector.add(new Double(0));
		}
		
		Double[] f = new Double[vector.size()];
		vector.toArray(f);
		double[] feature = new double[f.length];
		for (int i = 0; i < f.length; i++) {
			feature[i] = f[i];
		}
		return feature;
	}
	
	public double[] getMaxFeature() {
		double[] max = new double[this.featureList.get(0).length];
		Iterator<double[]> iter = featureList.iterator();
		while (iter.hasNext()) {
			double[] f = iter.next();
			for (int i = 0; i < max.length; i++) {
				if (max[i] < f[i]) {
					max[i] = f[i];
				}
			}
		}
		return max;
	}
	
	public void normalize(double[] max) {
		Iterator<double[]> iter = featureList.iterator();
		List<double[]> norm = new ArrayList<double[]>();
		while (iter.hasNext()) {
			double[] f = iter.next();
			for (int i = 0; i < f.length; i++) {
				f[i] /= max[i];
			}
			norm.add(f);
		}
		featureList = norm;
	}
	
}
